package com.atguigu.tingshu.common.login;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Author GQS @Date 2024/11/1/9:16 @Description
 * 登录注解的环绕通知 切面类
 */
@Aspect
@Component
public class GuiguLoginAspect {

  @Autowired
  private RedisTemplate redisTemplate;

  @SneakyThrows
  @Around("@annotation(com.atguigu.tingshu.common.login.GuiguLogin)")
  public Object loginAspect(ProceedingJoinPoint joinPoint){
    Object res = null;
    // 获取方法签名 (强转)
    MethodSignature signature = (MethodSignature)joinPoint.getSignature();
    // 拿到当前调用的方法对象
    Method method = signature.getMethod();
    // 获取方法的注解
    GuiguLogin guiGuLogin = method.getAnnotation(GuiguLogin.class);
    // 获取注解的requireLogin属性 判断是否需要登录校验
    if (guiGuLogin.requireLogin()){
      // 登录校验
      // 获取请求的包装对象 强转
      ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
      // 获取请求的对象
      HttpServletRequest request = requestAttributes.getRequest();
      // 获取请求头中的token参数
      String token = request.getHeader("token");
      // token为空 引导登录
      if (StringUtils.isEmpty(token)){
        throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
      }
      // token 合法性校验
      // 解码并验证JWT令牌 使用公钥
      Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(SystemConstant.PUBLIC_KEY));
      // 获取token的载荷
      JSONObject payLoad = JSONObject.parseObject(jwt.getClaims());
      // 从payload中获取userId
      Long userId = Long.parseLong(payLoad.get("userId").toString());
      // 从payload中获取vip状态
      Integer isVip = Integer.valueOf(payLoad.get("isVip").toString());
      // 抛异常则引导登录

      // token是私钥生成的合法token 但是 jti 解决重复-多端登录的问题 只认最后一次
      // 根据用户ID从Redis中获取用户登录信息
      String key = "User_Login_Id:" + userId;
      Object o = redisTemplate.opsForValue().get(key);
      // 检查对象是否为空或令牌不匹配，以防止未授权的访问
      if (o == null || !token.equals(o.toString())){
        // 如果条件满足，抛出登录认证异常
        throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
      }
      // 获取指定键的过期时间，单位为小时
      Long expireTime = redisTemplate.getExpire(key, TimeUnit.HOURS);
      // 如果过期时间小于1小时，则重新设置过期时间为1天
      if (expireTime < 1) {
        redisTemplate.expire(key, 1, TimeUnit.DAYS);
      }
      // 在方法的执行前 将userId存放到本地线程变量
      AuthContextHolder.setUserId(userId);
      // 存放用户的VIP信息到本地线程变量中
      AuthContextHolder.setIsVip(isVip);
    }
    // 获取方法的参数
    Object[] args = joinPoint.getArgs();
    // 执行方法
    res = joinPoint.proceed(args);
    //方法执行完成后, 删除本地线程中的userId 和 isVip
    AuthContextHolder.removeUserId();
    AuthContextHolder.removeIsVip();
    // 返回结果
    return res;
  }

}
